﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace StrategyGame
{
    class Nation
	{
		#region Static resources
		
		static private string[] GreekCityNames = new string[] { "Athens", "Sparta", "Alexandria", "Halicarnassus" };
		static private string[] SumerianCityNames = new string[] { "Uruk", "Akkad", "Eridu", "Lagash" };
		static private string[] BritishCityNames = new string[] { "Londinium", "Mamucium", "Eboracum", "Dubris" };
		static private string[] JapaneseCityNames = new string[] { "Tokyo", "Nagoya", "Kamogawa", "Yokohama" };
		static private string[] MoeishCityNames = new string[] { "Fukopia", "Yuunopolis", "Kaminaton" };
		
		static public readonly Dictionary<NationID, NationInfo> NationPresets;

		static Nation()
		{
			NationPresets = new Dictionary<NationID, NationInfo>();

			/* I'm going to hate you if this gets in. You deserve this. 
			Also, I'd prefer not to do a 'Greece' like this because I'm all ancient history and shit. It don't not make sense. 
			EDIT: I'm at least changing it to 'Greek States'. The same will probably go for other nations. 
			I know how much 'Greece' as all the ancient city-states can annoy me, so I'd like to do the research to make others somewhat accurate (at least in terminology too). 
			I know I'm being an awkward foo', but whatevs. Peace in. */
			NationPresets[NationID.Greece] = new NationInfo("Iskandar the Bro", "Greek States", Color.Maroon, GreekCityNames);
			NationPresets[NationID.Sumer] = new NationInfo("Gilgamesh the Arrogant", "Sumerian", Color.Gold, SumerianCityNames);
			NationPresets[NationID.Britain] = new NationInfo("Arthur the Effeminate", "British", Color.RoyalBlue, BritishCityNames);
			NationPresets[NationID.Japan] = new NationInfo("Toori the Impossible", "Japanese", Color.White, JapaneseCityNames);
			NationPresets[NationID.Moeland] = new NationInfo("Simon the Weeaboo", "Moeish", Color.HotPink, MoeishCityNames);
		}

		#endregion
		
		#region Nation-specific ID, colors and strings
		public struct NationInfo
		{
			public readonly string LeaderName;
			public readonly string Demonym;     //eg "Greek"
			public readonly Color NationalColor;

			public readonly string[] CityList;

			public NationInfo(string leader, string demonym, Color national_color, string[] city_list)
			{
				LeaderName = leader;
				Demonym = demonym;
				NationalColor = national_color;
				CityList = city_list;
			}
		}

		public enum NationID
		{
			Greece,
			Sumer,
			Britain,
			Japan,
			Moeland
		}

		public string NextCityName()
		{
			int number_of_names = NationPresets[ID].CityList.Count();
			int next_city_index = Cities.Count();

			string name = NationPresets[ID].CityList[next_city_index % number_of_names];

			int loops = next_city_index / number_of_names;
			for (int i = 0;
				i < loops;
				++i)
			{
				name = "New " + name;
			}

			return name;
		}

		NationID ID;

		public string Name { get { return ID.ToString(); } }
		public string Demonym { get { return NationPresets[ID].Demonym; } }
		public Color NationalColor { get { return NationPresets[ID].NationalColor; } }
		public Color DarkerNationalColor
		{
			get
			{
				Color result = Color.Multiply(NationalColor, 0.5f);
				result.A = 255;

				return result;
			}
		}
		#endregion

		#region MapObjects that belong to this nation
		//nations now store the lists of units and cities - a tidier solution than having everything belong to the world map
		private List<City> Cities;
		private List<Unit> Units;

		//returns true if adding the city is allowed
		public bool AddCity(string city_name, int pos_x, int pos_y, TileMap map)
		{
			//check there isn't already a city at this location
			if (map.CityAtTile(pos_x, pos_y) == null)
			{
				Cities.Add(new City(map, new Point(pos_x, pos_y), city_name));
				return true;
			}
			else
			{
				return false;
			}
		}

		#endregion

		#region Active unit/active city
		public Unit ActiveUnit { get; private set; }

		public void ActivateNextUnit()
		{
			if (Units.Count != 0) //can't do anything if there's no units!
			{
				if (ActiveUnit == null)
				{
					//if there's no active unit, pick the first unit from the list
					ActiveUnit = Units[0];
				}
				else
				{
					//look up the currently active unit in the list and get its index
					int current_unit_index = Units.IndexOf(ActiveUnit);

					//increment the index
					int next_unit_index = current_unit_index + 1;

					if (next_unit_index == Units.Count()) //check if we've gone past the end of the list
					{
						next_unit_index = 0;
					}

					//activate the unit
					ActiveUnit = Units[next_unit_index];
				}
			}
			else
			{
				//just to be on the safe side, make sure ActiveUnit is null if the Units list is empty
				ActiveUnit = null;
			}
		}

		public void ActivateNextUnitWithMovesLeft()
		{
			//remember this for later...
			Unit old_active_unit = ActiveUnit;

			//this just controls the do {...} while loop
			bool done = false;

			do
			{
				//cycle through active units
				ActivateNextUnit();

				//until we find one that:
				// -is the same unit we had active before this function was called (so it's looped around - in this case, give up, there's no unit to select)
				// -or has free movement points
				if (ActiveUnit == old_active_unit || ActiveUnit.GetRemainingMovePoints() != 0)
					done = true;
			}
			while (!done);

			//and finally, if the new unit active is the same as the old one, deselect it - that means none of our units have move left
			if (ActiveUnit == old_active_unit)
				ActiveUnit = null;
		}

		public bool AddUnit(Unit unit)
		{
			Units.Add(unit);

			return true;
		}

		public City ActiveCity { get; private set; }
		public void NextCity()
		{
			if (Cities.Count < 1)
			{
				ActiveCity = null;
			}
			else
			{
				int current_index = Cities.IndexOf(ActiveCity);

				if (current_index < 0 || current_index + 1 >= Cities.Count)
					ActiveCity = Cities[0];
				else
					ActiveCity = Cities[current_index + 1];
			}
		}
		#endregion

		#region Research & tech tree
		public TechTree Research { get; private set; }
		#endregion

		#region Visbility & fog-of-war
		//a hashset is a list of objects that doesn't contain duplicates
		//tiles that are revealed on the map (may or may not be in the fog of war)
		private HashSet<Point> KnownTiles = new HashSet<Point>();

		//tiles that are currently in our vision
		private HashSet<Point> VisibleTiles = new HashSet<Point>();

		//a public function to check if this nation has vision of a particular tile
		public bool CanSeeTile(int x, int y)
		{
			Point check_coords = new Point(x, y);
			return VisibleTiles.Contains(check_coords) || CHEAT_FogOfWarDisabled;
		}

		//runs every UpdateTurn and unit move to determine what we can and can't see
		public void CalculateVisibleTiles()
		{
			//the only difference between the way the VisibleTiles and KnownTiles lists work is that the visible tiles are cleared each turn
			//so it can only contain what is currently known
			VisibleTiles.Clear();

			//for now everything can just see one tile
			foreach (Unit unit in Units)
			{
				AddEveryTileAroundPointToVisibility(unit.Position.X, unit.Position.Y);
			}

			foreach (City city in Cities)
			{
				AddEveryTileAroundPointToVisibility(city.Position.X, city.Position.Y);

				foreach (Building building in city.Buildings)
				{
					AddEveryTileAroundPointToVisibility(building.Position.X, building.Position.Y);
				}
			}
		}

		/// <summary>
		/// Adds all the tiles in a 3x3 square around the chosen point to the currently visible set
		/// </summary>
		/// <param name="x">X position in map coords of the tile</param>
		/// <param name="y">Y position in map coords of the tile</param>
		private void AddEveryTileAroundPointToVisibility(int x, int y)
		{
			//add the center tile of course
			AddTileToVisibility(x, y);

			//in no particular order (ok it's clockwise) add the 8 surrounding tiles
			AddTileToVisibility(x - 1, y - 1);
			AddTileToVisibility(x, y - 1);
			AddTileToVisibility(x + 1, y - 1);
			AddTileToVisibility(x + 1, y);
			AddTileToVisibility(x + 1, y + 1);
			AddTileToVisibility(x, y + 1);
			AddTileToVisibility(x - 1, y + 1);
			AddTileToVisibility(x - 1, y);
		}

		/// <summary>
		/// Adds a single tile to the visbility lists
		/// </summary>
		/// <param name="x">X position in map coords of the tile</param>
		/// <param name="y">Y position in map coords of the tile</param>
		private void AddTileToVisibility(int x, int y)
		{
			Point tile_coords = new Point(x, y);

			//just put the coords into a Point object and store it in the set of visible tiles
			KnownTiles.Add(tile_coords);
			VisibleTiles.Add(tile_coords);
		}

		/// <summary>
		/// Is this tile in this nation's list of tiles that have been explored?
		/// </summary>
		/// <param name="x">X position in map coords of the tile</param>
		/// <param name="y">Y position in map coords of the tile</param>
		/// <returns>True if the tile is in the list otherwise false</returns>
		public bool HasRevealedTile(int x, int y)
		{
			Point point_to_check = new Point(x, y);

			return KnownTiles.Contains(point_to_check) || CHEAT_FogOfWarDisabled;
		}
		#endregion

		#region Rendering
		//in the draw functions pov_nation is the nation we're drawing from the point of view of (ie the player nation) for fog of war, stats etc
		public void DrawCities(SpriteBatch sprites, Nation pov_nation, int draw_offset_x, int draw_offset_y)
		{
			foreach (City city in Cities)
			{
				city.Draw(sprites, NationalColor, pov_nation, draw_offset_x, draw_offset_y);
			}
		}

		public void DrawLabels(SpriteBatch sprites, Nation pov_nation, int draw_offset_x, int draw_offset_y)
		{
			foreach (City city in Cities)
			{
				city.DrawTextLabels(sprites, NationalColor, pov_nation, draw_offset_x, draw_offset_y);
			}
		}

		public void DrawUnits(SpriteBatch sprites, Nation pov_nation, int draw_offset_x, int draw_offset_y)
		{
			foreach (Unit unit in Units)
			{
				unit.Draw(sprites, NationalColor, pov_nation, draw_offset_x, draw_offset_y);
			}
		}
		#endregion

		#region Utility/information functions
		//finds a city belonging to us on the specified map at the specified coordinates
		public City CityAt(int pos_x, int pos_y, TileMap map)
		{
			City result = null; //default result: null

			//go through all our cities
			foreach (City city in Cities)
			{
				//if this city is at pos_x, pos_y then set result to this city
				if (city.Position.X == pos_x && city.Position.Y == pos_y && city.Map == map)
					result = city;
			}

			//so the city at this location is returned if there is one, otherwise null is returned
			return result;
		}

		/// <summary>
		/// Checks whether this nation owns a given unit
		/// </summary>
		/// <param name="unit_to_check">The unit in question</param>
		/// <returns>True if this Nation has the unit in its Units list, otherwise false</returns>
		public bool OwnsUnit(Unit unit_to_check)
		{
			return Units.Contains(unit_to_check);
		}

		/// <summary>
		/// Checks whether this nation owns a given city
		/// </summary>
		/// <param name="city_to_check">The city in question</param>
		/// <returns>True if this Nation has the city in its Cities list, otherwise false</returns>
		public bool OwnsCity(City city_to_check)
		{
			return Cities.Contains(city_to_check);
		}

		public Building BuildingAtTile(TileMap map, Point coordinates)
		{
			foreach (City city in Cities)
			{
				Building building = city.BuildingAtTile(coordinates);

				if (building != null)
					return building;
			}

			return null;
		}
		#endregion

		#region Cheats
		bool CHEAT_FogOfWarDisabled = false;
		public void CHEAT_SetFogOfWar(bool state)
		{
			CHEAT_FogOfWarDisabled = !state;
		}

		public void CHEAT_RevealMap(TileMap map)
		{
			for (int x = 0;
				x < map.Width;
				++x)
				for (int y = 0;
					y < map.Height;
					++y)
				{
					AddTileToVisibility(x, y);
				}

			CalculateVisibleTiles();
		}

		public void CHEAT_UnRevealMap(TileMap map)
		{
			VisibleTiles.Clear();
			KnownTiles.Clear();

			CalculateVisibleTiles();
		}
		#endregion

		public void UpdateTurn(int turn_number)
		{
			foreach (City city in Cities)
			{
				city.UpdateTurn(turn_number);
			}

			//todo: separate this
			foreach (Unit unit in Units)
			{
				unit.UpdateTurn(turn_number);
			}

			CalculateVisibleTiles();
		}

		public Nation(NationID nation)
		{
			ID = nation;

			Cities = new List<City>();
			Units = new List<Unit>();

			Research = new TechTree();
		}
	}
}
